﻿using System;
using System.Collections.Generic;
using ArmyManager.Items;
using System.Linq;

namespace ArmyManager.Utils
{
	public class ItemHeap
	{
		private readonly ItemAttribute.AttributeTypes _attributeType;
		private readonly Dictionary<Item.ItemTypes, List<Tuple<int, Item>>> _items = new Dictionary<Item.ItemTypes, List<Tuple<int, Item>>>();

		public Dictionary<Item.ItemTypes, List<Tuple<int, Item>>>.KeyCollection ItemTypes { get { return _items.Keys; } }

		public List<Item> ItemsList
		{
			get
			{
				List<Item> returnList = new List<Item>();
				foreach (List<Tuple<int, Item>> items in _items.Values)
				{
					returnList.AddRange(items.Select(t => t.Item2));
				}
				return returnList;
			}
		}

		public ItemHeap(IEnumerable<Item> items, ItemAttribute.AttributeTypes attributeType)
		{
			_attributeType = attributeType;
			IEnumerable<IGrouping<Item.ItemTypes, Tuple<int, Item>>> grouped = items
				.Select(GetTuple)
				.OrderByDescending(t => t.Item1)
				.GroupBy(t => t.Item2.ItemType);
			foreach (IGrouping<Item.ItemTypes, Tuple<int, Item>> grouping in grouped)
			{
				_items.Add(grouping.Key, grouping.ToList());
			}
		}

		private Tuple<int, Item> GetTuple(Item item)
		{
			ItemAttribute attribute = item.Attributes.FirstOrDefault(a => a.AttributeType == _attributeType);
			int amount = attribute != null ? attribute.Amount : 0;
			return new Tuple<int, Item>(amount, item);
		}

		public Item Pop(Item.ItemTypes itemType)
		{
			return GetTopItem(itemType, removeItem: true);
		}

		public Item Peek(Item.ItemTypes itemType)
		{
			return GetTopItem(itemType, removeItem: false);
		}

		private Item GetTopItem(Item.ItemTypes itemType, bool removeItem)
		{
			List<Tuple<int, Item>> items;
			if (IsEmpty(itemType, out items))
			{
				return null;
			}
			Item item = items[0].Item2;
			if (removeItem)
			{
				SwapItems(items, 0, items.Count - 1);
				items.RemoveAt(items.Count - 1);
				HeapifyDown(items, 0);
			}
			return item;
		}

		private void HeapifyDown(List<Tuple<int, Item>> items, int index)
		{
			if (items.Count <= 0)
			{
				return;
			}

			int nextIndex = -1, highestAmount = items[index].Item1,
				indexLeft = 2*index + 1, indexRight = indexLeft + 1;

			if(indexLeft < items.Count && items[indexLeft].Item1 > highestAmount)
			{
				nextIndex = indexLeft;
				highestAmount = items[indexLeft].Item1;
			}
			if(indexRight < items.Count && items[indexRight].Item1 > highestAmount)
			{
				nextIndex = indexRight;
			}
			if (nextIndex < 0)
			{
				return;
			}
			SwapItems(items, index, nextIndex);
			HeapifyDown(items, nextIndex);
		}

		public bool IsEmpty(Item.ItemTypes itemType)
		{
			List<Tuple<int, Item>> items;
			return IsEmpty(itemType, out items);
		}

		private bool IsEmpty(Item.ItemTypes itemType, out List<Tuple<int, Item>> items)
		{
			return !_items.TryGetValue(itemType, out items) || items.Count == 0;
		}

		public void Push(Item item)
		{
			Tuple<int, Item> itemData = GetTuple(item);
			List<Tuple<int, Item>> items;
			if(!_items.TryGetValue(item.ItemType, out items))
			{
				items = new List<Tuple<int, Item>>();
				_items[item.ItemType] = items;
			}
			items.Add(itemData);
			HeapifyUp(items, items.Count - 1);
		}

		private void HeapifyUp(List<Tuple<int, Item>> items, int index)
		{
			if (index <= 0)
			{
				return;
			}
			int parent = (index - 1)/2;
			if (items[parent].Item1 >= items[index].Item1)
			{
				return;
			}
			SwapItems(items, index, parent);
			HeapifyUp(items, parent);
		}

		private void SwapItems(List<Tuple<int, Item>> items, int index1, int index2)
		{
			Tuple<int, Item> temp = items[index1];
			items[index1] = items[index2];
			items[index2] = temp;
		}

		public bool IsEmpty()
		{
			return _items.Keys.All(IsEmpty);
		}
	}
}